// ageMap.go - generic implementation of a map that can be pruned of
//             outdated entries

package ageMap

import (
    "container/list"
    "sync"
    "time"
)

// Map of list.Elements containing Agers. Elements themselves are arranged in
// the list in chronological order. All methods act on both structures in sync.
type AgeMap struct {
    contentMap map[string]*list.Element
    listByAge  *list.List
    mtx        *sync.RWMutex
}

// interface for a data type that knows its time of creation and how long it
// should live.
type Ager interface {
    Key() string
    Created() time.Time
    Lifetime() time.Duration
}

// Start from front of list and remove all old nodes from map and list. Stop
// when a timely entry is found.
func (m *AgeMap) Prune() {
    now := time.Now()
    for node := m.listByAge.Front(); node != nil; node = node.Next() {
        item := node.Value.(Ager)
        if now.Sub(item.Created()) <= item.Lifetime() {
            break
        }
        delete(m.contentMap, item.Key())
        m.listByAge.Remove(node)
    }
}

// Returns item if it exists and is current, otherwise, nil.
func (m *AgeMap) Fetch(key string) Ager {
    m.mtx.RLock()
    defer m.mtx.RUnlock()

    node, ok := m.contentMap[key]
    if !ok {
        return nil
    }
    item := node.Value.(Ager)
    if time.Since(item.Created()) > item.Lifetime() {
        m.Prune()
        return nil
    }
    return item
}

// Store item while clearing out any old entries.
func (m *AgeMap) Store(item Ager) {
    m.mtx.Lock()
    defer m.mtx.Unlock()
    m.contentMap[item.Key()] = m.listByAge.PushBack(item)
    m.Prune()
}

// Remove item by key.
func (m *AgeMap) Remove(key string) {
    m.mtx.Lock()
    defer m.mtx.Unlock()

    v, ok := m.contentMap[key]
    if !ok {
        return
    }
    m.listByAge.Remove(v)
    delete(m.contentMap, key)
}

func New() *AgeMap {
    m := new(AgeMap)
    m.contentMap = make(map[string]*list.Element)
    m.listByAge = list.New()
    m.mtx = new(sync.RWMutex)
    return m
}
